This R Notebook is the complement to my blog post What Percent of the Top-Voted Comments in Reddit Threads Were Also 1st Comment?.

This notebook is licensed under the MIT License. If you use the code or data visualization designs contained within this notebook, it would be greatly appreciated if proper attribution is given back to this notebook and/or myself. Thanks! :)

1 Setup

Setup the R packages.

library(dplyr)
library(nycflights13)
library(igraph)
library(intergraph)
library(sna)
library(ggplot2)
library(ggnetwork)
library(viridis)
library(plotly)
library(htmlwidgets)

The nycflights13 package contains a flights dataset.

flights %>% head()

There are 336,776 flights in the dataset.

2 Build the Network

Getting the edge weights is a dplyr aggregation.

df_edges <- flights %>% group_by(origin, dest) %>% summarize(weight = n())
df_edges %>% arrange(desc(weight)) %>% head()

There are 224 total edges.

Add a colors column to edge for each origin which will eventually be used for final ggplot.

# blue, red, green
colors = c("#3498db", "#e74c3c", "#2ecc71")
# seting alphabetical order; allows for predictable ordering later
origins = c("EWR", "JFK", "LGA")
df_colors = tbl_df(data.frame(origin=origins, color=origins))
df_edges <- df_edges %>% left_join(df_colors)
Joining, by = "origin"
joining factor and character vector, coercing into character vector
df_edges %>% arrange(desc(weight)) %>% head()
net <- graph.data.frame(df_edges, directed = T)
V(net)$degree <- centralization.degree(net)$res
V(net)$weighted_degree <- graph.strength(net)
V(net)$color_v <- c(origins, rep("Others", gorder(net) - length(colors)))
net
IGRAPH DNW- 107 224 -- 
+ attr: name (v/c), degree (v/n), weighted_degree (v/n), color_v
| (v/c), weight (e/n), color (e/c)
+ edges (vertex names):
 [1] EWR->ALB EWR->ANC EWR->ATL EWR->AUS EWR->AVL EWR->BDL EWR->BNA EWR->BOS
 [9] EWR->BQN EWR->BTV EWR->BUF EWR->BWI EWR->BZN EWR->CAE EWR->CHS EWR->CLE
[17] EWR->CLT EWR->CMH EWR->CVG EWR->DAY EWR->DCA EWR->DEN EWR->DFW EWR->DSM
[25] EWR->DTW EWR->EGE EWR->FLL EWR->GRR EWR->GSO EWR->GSP EWR->HDN EWR->HNL
[33] EWR->HOU EWR->IAD EWR->IAH EWR->IND EWR->JAC EWR->JAX EWR->LAS EWR->LAX
[41] EWR->LGA EWR->MCI EWR->MCO EWR->MDW EWR->MEM EWR->MHT EWR->MIA EWR->MKE
[49] EWR->MSN EWR->MSP EWR->MSY EWR->MTJ EWR->MYR EWR->OKC EWR->OMA EWR->ORD
+ ... omitted several edges

Write specialized hovertext for each vertex. Note that airport attributes must be mapped to same order as vertices.

df_airports <- data.frame(vname=V(net)$name) %>% left_join(airports, by=c("vname" = "faa"))
joining character vector and factor, coercing into character vector
V(net)$text <- paste(V(net)$name,
                       df_airports$name,
                       paste(format(V(net)$weighted_degree, big.mark=",", trim=T), "Flights"),
                        sep = "<br>")
V(net)$text %>% head()
[1] "EWR<br>Newark Liberty Intl<br>120,835 Flights"           
[2] "JFK<br>John F Kennedy Intl<br>111,279 Flights"           
[3] "LGA<br>La Guardia<br>104,663 Flights"                    
[4] "ALB<br>Albany Intl<br>439 Flights"                       
[5] "ANC<br>Ted Stevens Anchorage Intl<br>8 Flights"          
[6] "ATL<br>Hartsfield Jackson Atlanta Intl<br>17,215 Flights"

Add latitudes/longitudes to both vertices and edges for spatial map;

V(net)$lat <- df_airports$lat
V(net)$lon <- df_airports$lon
# gives to/from locations; map to corresponding ending lat/long
end_loc <- data.frame(ename=get.edgelist(net)[,2]) %>% left_join(airports, by=c("ename" = "faa"))
joining character vector and factor, coercing into character vector
E(net)$endlat <- end_loc$lat
E(net)$endlon <- end_loc$lon

3 Plotting the Network Graph

Use ggnetwork to transform the network to a ggplot friendly format.

df_net <- ggnetwork(net, layout = "fruchtermanreingold", weights="weight", niter=5000, arrow.gap=0)
df_net %>% head()
plot <- ggplot(df_net, aes(x = x, y = y, xend = xend, yend = yend)) +
    geom_edges(aes(color = color), size=0.4, alpha=0.25) +
    geom_nodes(aes(color = color_v, size = degree, text=text)) +
    ggtitle("Network Graph of U.S. Flights Outbound from NYC in 2013") +
    scale_color_manual(labels=c("LGA", "EWR", "JFK", "Others"), 
                         values=c(colors, "#1a1a1a"), name="Airports") +
    guides(size=FALSE) +
    theme_blank()
plot

plot %>% ggplotly(tooltip="text") %>% toWebGL()

Save interactive plot locally to disk using htmlwidgets for uploading to my website. (this only saves the data/layout; you will need to provide the relevant plot.ly javascript on your own website)

plot %>% ggplotly(tooltip="text", width="100%", height=600) %>%
    saveWidget("ggplot-graph-1.html", selfcontained=F, libdir="plotly")

3.1 Plot by Physical Location

plot <- ggplot(df_net, aes(x = lon, y = lat, xend = endlon, yend = endlat)) +
    geom_edges(aes(color = color), size=0.4, alpha=0.25) +
    geom_nodes(aes(color = color_v, size = degree, text=text)) +
    ggtitle("Locations of U.S. Flights Outbound from NYC in 2013") +
    scale_color_manual(labels=c("LGA", "EWR", "JFK", "Others"), 
                         values=c(colors, "#1a1a1a"), name="Airports") +
    guides(size=FALSE) +
    theme_blank()
plot

plot %>% ggplotly(tooltip="text") %>% toWebGL()
plot %>% ggplotly(tooltip="text", width="100%", height=600) %>%
    saveWidget("ggplot-graph-2.html", selfcontained=F, libdir="plotly")

4 LICENSE

The MIT License (MIT)

Copyright (c) 2016 Max Woolf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

LS0tCnRpdGxlOiAiSG93IHRvIENyZWF0ZSBhbiBJbnRlcmFjdGl2ZSBXZWJHTCBOZXR3b3JrIEdyYXBoIFVzaW5nIFIiCmF1dGhvcjogIk1heCBXb29sZiAoQG1pbmltYXhpcikiCmRhdGU6ICJOb3ZlbWJlciAxNXRoLCAyMDE2IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIG1hdGhqYXg6IG51bGwKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KClRoaXMgUiBOb3RlYm9vayBpcyB0aGUgY29tcGxlbWVudCB0byBteSBibG9nIHBvc3QgW1doYXQgUGVyY2VudCBvZiB0aGUgVG9wLVZvdGVkIENvbW1lbnRzIGluIFJlZGRpdCBUaHJlYWRzIFdlcmUgQWxzbyAxc3QgQ29tbWVudD9dKGh0dHA6Ly9taW5pbWF4aXIuY29tLzIwMTYvMTEvZmlyc3QtY29tbWVudC8pLgoKVGhpcyBub3RlYm9vayBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIElmIHlvdSB1c2UgdGhlIGNvZGUgb3IgZGF0YSB2aXN1YWxpemF0aW9uIGRlc2lnbnMgY29udGFpbmVkIHdpdGhpbiB0aGlzIG5vdGVib29rLCBpdCB3b3VsZCBiZSBncmVhdGx5IGFwcHJlY2lhdGVkIGlmIHByb3BlciBhdHRyaWJ1dGlvbiBpcyBnaXZlbiBiYWNrIHRvIHRoaXMgbm90ZWJvb2sgYW5kL29yIG15c2VsZi4gVGhhbmtzISA6KQoKIyBTZXR1cAoKU2V0dXAgdGhlIFIgcGFja2FnZXMuCgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShueWNmbGlnaHRzMTMpCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KGludGVyZ3JhcGgpCmxpYnJhcnkoc25hKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2duZXR3b3JrKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KGh0bWx3aWRnZXRzKQpgYGAKClRoZSBgbnljZmxpZ2h0czEzYCBwYWNrYWdlIGNvbnRhaW5zIGEgYGZsaWdodHNgIGRhdGFzZXQuCgpgYGB7cn0KZmxpZ2h0cyAlPiUgaGVhZCgpCmBgYAoKVGhlcmUgYXJlICoqYHIgZmxpZ2h0cyAlPiUgbnJvdygpICU+JSBmb3JtYXQoYmlnLm1hcms9IiwiKWAqKiBmbGlnaHRzIGluIHRoZSBkYXRhc2V0LgoKIyBCdWlsZCB0aGUgTmV0d29yawoKR2V0dGluZyB0aGUgZWRnZSB3ZWlnaHRzIGlzIGEgYGRwbHlyYCBhZ2dyZWdhdGlvbi4KCmBgYHtyfQpkZl9lZGdlcyA8LSBmbGlnaHRzICU+JSBncm91cF9ieShvcmlnaW4sIGRlc3QpICU+JSBzdW1tYXJpemUod2VpZ2h0ID0gbigpKQpkZl9lZGdlcyAlPiUgYXJyYW5nZShkZXNjKHdlaWdodCkpICU+JSBoZWFkKCkKYGBgCgpUaGVyZSBhcmUgKipgciBkZl9lZGdlcyAlPiUgbnJvdygpICU+JSBmb3JtYXQoYmlnLm1hcms9IiwiKWAqKiB0b3RhbCBlZGdlcy4KCkFkZCBhIGNvbG9ycyBjb2x1bW4gdG8gZWRnZSBmb3IgZWFjaCBgb3JpZ2luYCB3aGljaCB3aWxsIGV2ZW50dWFsbHkgYmUgdXNlZCBmb3IgZmluYWwgZ2dwbG90LgoKYGBge3J9CiMgYmx1ZSwgcmVkLCBncmVlbgpjb2xvcnMgPSBjKCIjMzQ5OGRiIiwgIiNlNzRjM2MiLCAiIzJlY2M3MSIpCgojIHNldGluZyBhbHBoYWJldGljYWwgb3JkZXI7IGFsbG93cyBmb3IgcHJlZGljdGFibGUgb3JkZXJpbmcgbGF0ZXIKb3JpZ2lucyA9IGMoIkVXUiIsICJKRksiLCAiTEdBIikKCmRmX2NvbG9ycyA9IHRibF9kZihkYXRhLmZyYW1lKG9yaWdpbj1vcmlnaW5zLCBjb2xvcj1vcmlnaW5zKSkKZGZfZWRnZXMgPC0gZGZfZWRnZXMgJT4lIGxlZnRfam9pbihkZl9jb2xvcnMpCgpkZl9lZGdlcyAlPiUgYXJyYW5nZShkZXNjKHdlaWdodCkpICU+JSBoZWFkKCkKYGBgCgoKYGBge3J9Cm5ldCA8LSBncmFwaC5kYXRhLmZyYW1lKGRmX2VkZ2VzLCBkaXJlY3RlZCA9IFQpClYobmV0KSRkZWdyZWUgPC0gY2VudHJhbGl6YXRpb24uZGVncmVlKG5ldCkkcmVzClYobmV0KSR3ZWlnaHRlZF9kZWdyZWUgPC0gZ3JhcGguc3RyZW5ndGgobmV0KQpWKG5ldCkkY29sb3JfdiA8LSBjKG9yaWdpbnMsIHJlcCgiT3RoZXJzIiwgZ29yZGVyKG5ldCkgLSBsZW5ndGgoY29sb3JzKSkpCgpuZXQKYGBgCgpXcml0ZSBzcGVjaWFsaXplZCBob3ZlcnRleHQgZm9yIGVhY2ggdmVydGV4LiBOb3RlIHRoYXQgYWlycG9ydCBhdHRyaWJ1dGVzIG11c3QgYmUgbWFwcGVkIHRvIHNhbWUgb3JkZXIgYXMgdmVydGljZXMuCgoKYGBge3J9CgpkZl9haXJwb3J0cyA8LSBkYXRhLmZyYW1lKHZuYW1lPVYobmV0KSRuYW1lKSAlPiUgbGVmdF9qb2luKGFpcnBvcnRzLCBieT1jKCJ2bmFtZSIgPSAiZmFhIikpCgpWKG5ldCkkdGV4dCA8LSBwYXN0ZShWKG5ldCkkbmFtZSwKICAgICAgICAgICAgICAgICAgICAgICBkZl9haXJwb3J0cyRuYW1lLAogICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKGZvcm1hdChWKG5ldCkkd2VpZ2h0ZWRfZGVncmVlLCBiaWcubWFyaz0iLCIsIHRyaW09VCksICJGbGlnaHRzIiksCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICI8YnI+IikKClYobmV0KSR0ZXh0ICU+JSBoZWFkKCkKYGBgCgpBZGQgbGF0aXR1ZGVzL2xvbmdpdHVkZXMgdG8gYm90aCB2ZXJ0aWNlcyBhbmQgZWRnZXMgZm9yIHNwYXRpYWwgbWFwOwoKYGBge3J9ClYobmV0KSRsYXQgPC0gZGZfYWlycG9ydHMkbGF0ClYobmV0KSRsb24gPC0gZGZfYWlycG9ydHMkbG9uCgojIGdpdmVzIHRvL2Zyb20gbG9jYXRpb25zOyBtYXAgdG8gY29ycmVzcG9uZGluZyBlbmRpbmcgbGF0L2xvbmcKZW5kX2xvYyA8LSBkYXRhLmZyYW1lKGVuYW1lPWdldC5lZGdlbGlzdChuZXQpWywyXSkgJT4lIGxlZnRfam9pbihhaXJwb3J0cywgYnk9YygiZW5hbWUiID0gImZhYSIpKQoKRShuZXQpJGVuZGxhdCA8LSBlbmRfbG9jJGxhdApFKG5ldCkkZW5kbG9uIDwtIGVuZF9sb2MkbG9uCmBgYAoKIyBQbG90dGluZyB0aGUgTmV0d29yayBHcmFwaAoKVXNlIGBnZ25ldHdvcmtgIHRvIHRyYW5zZm9ybSB0aGUgbmV0d29yayB0byBhIGBnZ3Bsb3RgIGZyaWVuZGx5IGZvcm1hdC4KCmBgYHtyfQpkZl9uZXQgPC0gZ2duZXR3b3JrKG5ldCwgbGF5b3V0ID0gImZydWNodGVybWFucmVpbmdvbGQiLCB3ZWlnaHRzPSJ3ZWlnaHQiLCBuaXRlcj01MDAwLCBhcnJvdy5nYXA9MCkKZGZfbmV0ICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfbmV0LCBhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geGVuZCwgeWVuZCA9IHllbmQpKSArCiAgICBnZW9tX2VkZ2VzKGFlcyhjb2xvciA9IGNvbG9yKSwgc2l6ZT0wLjQsIGFscGhhPTAuMjUpICsKICAgIGdlb21fbm9kZXMoYWVzKGNvbG9yID0gY29sb3Jfdiwgc2l6ZSA9IGRlZ3JlZSwgdGV4dD10ZXh0KSkgKwogICAgZ2d0aXRsZSgiTmV0d29yayBHcmFwaCBvZiBVLlMuIEZsaWdodHMgT3V0Ym91bmQgZnJvbSBOWUMgaW4gMjAxMyIpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHM9YygiTEdBIiwgIkVXUiIsICJKRksiLCAiT3RoZXJzIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzPWMoY29sb3JzLCAiIzFhMWExYSIpLCBuYW1lPSJBaXJwb3J0cyIpICsKICAgIGd1aWRlcyhzaXplPUZBTFNFKSArCiAgICB0aGVtZV9ibGFuaygpCgpwbG90CmBgYAoKYGBge3J9CnBsb3QgJT4lIGdncGxvdGx5KHRvb2x0aXA9InRleHQiKSAlPiUgdG9XZWJHTCgpCmBgYAoKU2F2ZSBpbnRlcmFjdGl2ZSBwbG90IGxvY2FsbHkgdG8gZGlzayB1c2luZyBgaHRtbHdpZGdldHNgIGZvciB1cGxvYWRpbmcgdG8gbXkgd2Vic2l0ZS4gKHRoaXMgb25seSBzYXZlcyB0aGUgZGF0YS9sYXlvdXQ7IHlvdSB3aWxsIG5lZWQgdG8gcHJvdmlkZSB0aGUgcmVsZXZhbnQgcGxvdC5seSBqYXZhc2NyaXB0IG9uIHlvdXIgb3duIHdlYnNpdGUpCgpgYGB7cn0KcGxvdCAlPiUgZ2dwbG90bHkodG9vbHRpcD0idGV4dCIsIHdpZHRoPSIxMDAlIiwgaGVpZ2h0PTYwMCkgJT4lCiAgICBzYXZlV2lkZ2V0KCJnZ3Bsb3QtZ3JhcGgtMS5odG1sIiwgc2VsZmNvbnRhaW5lZD1GLCBsaWJkaXI9InBsb3RseSIpCmBgYAoKIyMgUGxvdCBieSBQaHlzaWNhbCBMb2NhdGlvbgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX25ldCwgYWVzKHggPSBsb24sIHkgPSBsYXQsIHhlbmQgPSBlbmRsb24sIHllbmQgPSBlbmRsYXQpKSArCiAgICBnZW9tX2VkZ2VzKGFlcyhjb2xvciA9IGNvbG9yKSwgc2l6ZT0wLjQsIGFscGhhPTAuMjUpICsKICAgIGdlb21fbm9kZXMoYWVzKGNvbG9yID0gY29sb3Jfdiwgc2l6ZSA9IGRlZ3JlZSwgdGV4dD10ZXh0KSkgKwogICAgZ2d0aXRsZSgiTG9jYXRpb25zIG9mIFUuUy4gRmxpZ2h0cyBPdXRib3VuZCBmcm9tIE5ZQyBpbiAyMDEzIikgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKGxhYmVscz1jKCJMR0EiLCAiRVdSIiwgIkpGSyIsICJPdGhlcnMiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXM9Yyhjb2xvcnMsICIjMWExYTFhIiksIG5hbWU9IkFpcnBvcnRzIikgKwogICAgZ3VpZGVzKHNpemU9RkFMU0UpICsKICAgIHRoZW1lX2JsYW5rKCkKCnBsb3QKYGBgCgpgYGB7cn0KcGxvdCAlPiUgZ2dwbG90bHkodG9vbHRpcD0idGV4dCIpICU+JSB0b1dlYkdMKCkKYGBgCgpgYGB7cn0KcGxvdCAlPiUgZ2dwbG90bHkodG9vbHRpcD0idGV4dCIsIHdpZHRoPSIxMDAlIiwgaGVpZ2h0PTYwMCkgJT4lCiAgICBzYXZlV2lkZ2V0KCJnZ3Bsb3QtZ3JhcGgtMi5odG1sIiwgc2VsZmNvbnRhaW5lZD1GLCBsaWJkaXI9InBsb3RseSIpCmBgYAoKIyBMSUNFTlNFCgpUaGUgTUlUIExpY2Vuc2UgKE1JVCkKCkNvcHlyaWdodCAoYykgMjAxNiBNYXggV29vbGYKClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkgb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwgaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cyB0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcyBmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsIGNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUiBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIgTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUgU09GVFdBUkUu